<?php

require_once('Yubikey.php');

/**
  * SquirrelMail Yubikey Plugin
  *
  * @copyright &copy; 2009 Richard Ferguson <yubikey@fergusnet.com>
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version 0.6
  * @package plugins
  * @subpackage yubikey
  */

/* insert options into personal preferences */
function yubikey_options_do()
{
	global $optpage_data;

	/* get saved information */
	global $username, $data_dir;
	$require_yubikey =  getPref($data_dir,$username,'yubikey_required','No');
	$yubikey_id = getPref($data_dir,$username,'yubikey_id');
	$yubikey_id_backup = getPref($data_dir,$username,'yubikey_id_backup');
	
    $optpage_data['grps']['yubikey'] = _("Yubikey Options");
    $optionValues = array();
    $optionValues[] = array(
        'name'    => 'yubikey_required',
        'caption' => _("Require Yubikey for Authentication"),
        'type'    => SMOPT_TYPE_BOOLEAN,
		'refresh' => SMOPT_REFRESH_NONE,
		'initial_value' => $require_yubikey
    );
    $optionValues[] = array(
        'name'    => 'yubikey_id',
        'caption' => _("Primary Yubikey ID"),
        'type'    => SMOPT_TYPE_STRING,
		'refresh' => SMOPT_REFRESH_NONE,
		'initial_value' => $yubikey_id,
		'save' => 'yubikey_save_id'
	);
    $optionValues[] = array(
        'name'    => 'yubikey_id_backup',
        'caption' => _("Backup Yubikey ID"),
        'type'    => SMOPT_TYPE_STRING,
		'refresh' => SMOPT_REFRESH_NONE,
		'initial_value' => $yubikey_id_backup,
		'save' => 'yubikey_save_id'
	);
    $optpage_data['vals']['yubikey'] = $optionValues;
}

/* truncate yubikey id to 12 chars */
function yubikey_save_id($option) 
{
	if (strlen($option->new_value) > 12) {
		/* if the user entered a full yubikey otp we
		 * must authenticate it so it can't be used */
		if (strlen($option->new_value) == 44) {
			global $yubico_server_id, $yubico_server_key, $yubico_server_url;
			$yubi = new Yubikey($yubico_server_id);
			if ($yubico_server_key) {
				$yubi->setKey($yubico_server_key);
			}
			if ($yubico_server_url) {
				$yubi->setUrl($yubico_server_url);
			}
			$yubi->setCurlTimeout(10);
			$yubi->setTimestampTolerance(600);
			if (!$yubi->verify($option->new_value)) {
				/* verification failed! Don't save ID */
				set_up_language($squirrelmail_language, true);
				include_once(SM_PATH . 'functions/display_messages.php' );
				error_option_save( _("Invalid Yubikey OTP, Yubikey ID not saved!"));
				return;
			}
		}
		$option->new_value = substr($option->new_value, 0, 12);
	}
	save_option($option);
}

/* add yubikey input box */
function yubikey_form_do()
{
	echo html_tag( 'table',
		html_tag( 'tr', 
		html_tag( 'td', _("Yubikey OTP:"), 'right', '', 'width="30%"' ) . 
		html_tag( 'td', 
			addInput('yubikeyotp', null, 0, 0, ' onfocus="alreadyFocused=true;"'),
			'left', '', 'width="70%"' )
		), 'center','', 'width="350"');
}

/* process yubikey otp from login form */
function yubikey_login_do()
{
	global $secretkey;

	/* we need at least 44 modhex characters for an OTP,
	 * but if we find 64 or more, assume its a Yubikey
	 * in static password mode.
	 */
	$mhcnt = count_modhex($secretkey);
	if ($mhcnt >= 44 and $mhcnt < 64) {
		$yubikey_otp = substr($secretkey, strlen($secretkey)-44, 44);
		$secretkey = substr($secretkey, 0, strlen($secretkey)-44);
		sqsession_register($yubikey_otp, 'yubikeyotp');
	}
}

/* verify yubikey one time password */
function yubikey_verify_do()
{	
	global $username, $data_dir;
	
	$require_yubikey =  getPref($data_dir,$username,'yubikey_required',0);
	$yubikey_id = getPref($data_dir,$username,'yubikey_id',0);
	$yubikey_id_backup = getPref($data_dir,$username,'yubikey_id_backup',0);

	if ($require_yubikey && !empty($yubikey_id)) {
		/* user preference is requesting yubikey auth */
		sqGetGlobalVar('yubikeyotp',$yubikey_otp);
		if (!yubikey_authenticate($yubikey_otp, $yubikey_id, $yubikey_id_backup)) {
			/* yubikey authentication failed */
			set_up_language($squirrelmail_language, true);
			include_once(SM_PATH . 'functions/display_messages.php' );
			sqsession_destroy();
			/* terminate the session nicely */
			logout_error( _("Unknown user or password incorrect.") );
			exit;
		}
	}
}

/* communicate with Yubico's authentication service */
function yubikey_authenticate($otp, $id, $idbak)
{
	global $yubico_server_id, $yubico_server_key, $yubico_server_url;

	/* verify otp matches stored id */
	if (substr($otp, 0, 12) != substr($id, 0, 12)) {
		/* we don't match the primary id */
		if (!empty($idbak) && substr($otp, 0, 12) != substr($idbak, 0 , 12)) {
			/* we don't match the backup id */
			return false;
		}
	}

	/* authenticate against Yubico's servers */
	$yubi = new Yubikey($yubico_server_id);
	if ($yubico_server_key) {
		$yubi->setKey($yubico_server_key);
	}
	if ($yubico_server_url) {
		$yubi->setUrl($yubico_server_url);
	}
	$yubi->setCurlTimeout(10);
	$yubi->setTimestampTolerance(600);
	return $yubi->verify($otp);
}

/* count modhex chars at end of string */
function count_modhex($str)
{
	$cnt = 0;
	for ($i=strlen($str)-1;$i>=0;$i--) {
		/* cbde fghi jkln rtuv */
		$c = substr($str,$i,1);
		if ($c == 'c' or $c == 'b' or
			$c == 'd' or $c == 'e' or
			$c == 'f' or $c == 'g' or
			$c == 'h' or $c == 'i' or
			$c == 'j' or $c == 'k' or
			$c == 'l' or $c == 'n' or
			$c == 'r' or $c == 't' or
			$c == 'u' or $c == 'v') {
				$cnt++;
		}
	}	
	return $cnt;
}
